#include "middleware.h"

#include <opencv2/opencv.hpp>
#include <time.h>
#include <vector>

#include "hw_app_config.h"
#include "hw_app_log.h"
#include "cv_utils.h"
#include "image_cache.h"
#include "spdlog/stopwatch.h"

namespace gc
{
    static inline nlohmann::json getImageDataFromTrackObjs(vector<GCTrackObj> &track_objs, std::string result_plate, nlohmann::json &proofs)
    {
        // TODO 如果是上传智慧件，补充状态图
        for (auto &track_obj: track_objs) {
            nlohmann::json proof;
            if (strlen(track_obj.img_path) == 0) {
                LOG_ERROR("track_id=%d track_obj.img_path is empty", track_obj.track_id);
                continue;
            }
            cv::Mat frame_img;
            GCImageCache::GetInstance().Get(track_obj.img_path, frame_img);
            if (frame_img.empty()) {
                LOG_ERROR("track_id=%d frame_img not found in cache for img_path=%s", track_obj.track_id, track_obj.img_path);
                continue;
            }
            std::string base64 = Mat2Base64(frame_img, ".jpg");
            frame_img.release();              
            base64.insert(0, "data:image/jpeg;base64,");
            proof["url"] = base64;
            proof["time"] = track_obj.time_str;

            GCRect rect = track_obj.rect;
            int size = snprintf(nullptr, 0, "%2f,%2f,%2f,%2f,%2f,%2f", rect.x * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_w, rect.y * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_h, (rect.x + rect.width) * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_w, (rect.y + rect.height) * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_h, gc_app_config.base_config.alg_config.subscribe_yuv_img_w * 1.f, gc_app_config.base_config.alg_config.subscribe_yuv_img_h * 1.f);
            char car_pos[size+1];
            snprintf(&car_pos[0], sizeof(car_pos), "%2f,%2f,%2f,%2f,%2f,%2f", rect.x * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_w, rect.y * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_h, (rect.x + rect.width) * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_w, (rect.y + rect.height) * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_h, gc_app_config.base_config.alg_config.subscribe_yuv_img_w * 1.f, gc_app_config.base_config.alg_config.subscribe_yuv_img_h * 1.f);
            proof["carPosition"] = &car_pos[0];

            if (track_obj.lpr_result.empty() || track_obj.lpr_result["data"].empty() || track_obj.lpr_result["errcode"] != 0 ) {
                LOG_DEBUG("track_id=%d lpr_result of track_obj is empty, %s", track_obj.track_id, track_obj.lpr_result.dump().c_str());
                proofs.emplace_back(proof);
                continue;
            }
            bool plate_found = false;
            nlohmann::json plate_data_list = track_obj.lpr_result["data"];
            for (auto& plate_data : plate_data_list) {
                std::string plate = plate_data["plate"].get<std::string>();
                if (plate.compare(result_plate.c_str()) == 0) {
                    nlohmann::json box = plate_data["box"];
                    std::string color;
                    if (plate_data["color"].is_null()) {
                        color = "";
                    } else {
                        color = plate_data["color"];
                    }
                    int conf = plate_data["conf"].get<int>();
                    int plate_lx = rect.x + box.at(0).get<int>();
                    int plate_ty = rect.y + box.at(1).get<int>();
                    int plate_rx = rect.x + box.at(2).get<int>();
                    int plate_by = rect.y + box.at(3).get<int>();
                    // 前端截取车牌情况
                    if (track_obj.car_plate_boxes.size() == 1) {
                        GCRect plate_rect = track_obj.car_plate_boxes[0]; // TODO
                        plate_lx = plate_rect.x;
                        plate_ty = plate_rect.y;
                        plate_rx = plate_lx + plate_rect.width;
                        plate_by = plate_ty + plate_rect.height;
                    }
                    int size = snprintf(nullptr, 0, "%2f,%2f,%2f,%2f,%2f,%2f", plate_lx * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_w, plate_ty * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_h, plate_rx * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_w, plate_by * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_h, gc_app_config.base_config.alg_config.subscribe_yuv_img_w * 1.f, gc_app_config.base_config.alg_config.subscribe_yuv_img_h * 1.f);
                    char plate_pos[size+1];
                    snprintf(&plate_pos[0], sizeof(plate_pos), "%f,%f,%f,%f,%f,%f", plate_lx * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_w, plate_ty * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_h, plate_rx * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_w, plate_by * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_h, gc_app_config.base_config.alg_config.subscribe_yuv_img_w * 1.f, gc_app_config.base_config.alg_config.subscribe_yuv_img_h * 1.f);
                    proof["platePosition"] = &plate_pos[0];
                    proof["plateColor"] = PLATE_COLOR_MAP[color];
                    proof["plateConf"] = conf / 100.0;

                    plate_found = true;
                    break;
                }
            }
            if (!plate_found && track_obj.car_plate_boxes.size() == 1) {
                GCRect plate_rect = track_obj.car_plate_boxes[0]; // TODO
                int plate_lx = plate_rect.x;
                int plate_ty = plate_rect.y;
                int plate_rx = plate_lx + plate_rect.width;
                int plate_by = plate_ty + plate_rect.height;
                int size = snprintf(nullptr, 0, "%2f,%2f,%2f,%2f,%2f,%2f", plate_lx * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_w, plate_ty * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_h, plate_rx * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_w, plate_by * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_h, gc_app_config.base_config.alg_config.subscribe_yuv_img_w * 1.f, gc_app_config.base_config.alg_config.subscribe_yuv_img_h * 1.f);
                char plate_pos[size+1];
                snprintf(&plate_pos[0], sizeof(plate_pos), "%f,%f,%f,%f,%f,%f", plate_lx * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_w, plate_ty * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_h, plate_rx * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_w, plate_by * 100.f / gc_app_config.base_config.alg_config.subscribe_yuv_img_h, gc_app_config.base_config.alg_config.subscribe_yuv_img_w * 1.f, gc_app_config.base_config.alg_config.subscribe_yuv_img_h * 1.f);
                proof["platePosition"] = &plate_pos[0];
            }
            proofs.emplace_back(proof);
        }
        return proofs;
    }

    Middleware::Middleware()
    {
        m_client = nullptr;
        LOG_DEBUG("Middleware constructor");
    }

    Middleware::~Middleware(void)
    {
        delete m_client;
        LOG_DEBUG("Middleware destructor");
    }

    httplib::Client* Middleware::httpClient(void)
    {
        if (m_client != nullptr) {
            return m_client;
        }
        m_client = new httplib::Client("unix:/tmp/http_proxy_connect.socket");
        m_client->set_default_headers({ { "Host", gc_app_config.base_config.middleware_host.c_str() }, { "Proxy-Connection", "keep-alive" }, { "Accept-Encoding", "gzip, deflate" } });
        m_client->set_keep_alive(false);
        m_client->set_compress(true);
        m_client->set_logger([](const httplib::Request& req, const httplib::Response& res) {
            // LOG_DEBUG("http request: method=%s, path=%s, body=%s", req.method.c_str(), req.path.c_str(), req.body.c_str());
            LOG_INFO("http request: method=%s, %s:%d%s", req.method.c_str(), req.remote_addr.c_str(), req.remote_port, req.path.c_str());
            LOG_INFO("http response: status=%d, body=%s", res.status, res.body.c_str());
        });
        return m_client;
    }

    nlohmann::json Middleware::Upload_parking_status_image(const std::string& camera_key, cv::Mat mat, const char* time_str)
    {
        spdlog::stopwatch sw;

        // parking_collect_entry/newDeviceUpload/parkingStatus
        std::string base64 = Mat2Base64(mat, ".jpg");
        base64.insert(0, "data:image/jpeg;base64,");
        nlohmann::json body = {
            { "sn", camera_key },
            { "deviceTime", time_str },
            { "statusPictureUrl", base64 }, 
        };
        std::string content = body.dump();

        auto res = httpClient()->Post(gc_app_config.base_config.middleware_upload_image_api.c_str(), content, "application/json");
        int retry_time = 0;
        while ((res == nullptr || res->status != 200) && retry_time < 3) {
            usleep(100000);
            res = httpClient()->Post(gc_app_config.base_config.middleware_upload_image_api.c_str(), content, "application/json");
            retry_time ++;
        }
        nlohmann::json result_json;
        if (res == nullptr) {
            LOG_ERROR("POST upload image api response is null, middleware_host=%s", gc_app_config.base_config.middleware_host.c_str());
        } else if (res->status != 200) {
            LOG_ERROR("POST upload image api response status=%d, middleware_host=%s", res->status, gc_app_config.base_config.middleware_host.c_str());
        } else {
            try
            {
                result_json = nlohmann::json::parse(res->body.c_str());
            }
            catch(const std::exception& e)
            {
                LOG_ERROR("POST upload image api parse response error, middleware_host=%s, response=%s", gc_app_config.base_config.middleware_host.c_str(), res->body.c_str());
            }
        }

        logger->info("Upload_parking_status_image_time: {:.3} s", sw);

        return result_json;          
    }

    nlohmann::json Middleware::Upload_inout_data(GCTrackResult &track_result)
    {
        nlohmann::json result_json;
        spdlog::stopwatch sw;

        nlohmann::json proofs;
        nlohmann::json states;

        getImageDataFromTrackObjs(track_result.proof_track_objs, track_result.plate, proofs);
        if (!track_result.state_track_objs.empty()) {
            getImageDataFromTrackObjs(track_result.state_track_objs, track_result.plate, states);
            // 状态图的第一张图片文件可能已经被自动删除，此时补充为证据图的第一张，防止中间件显示顺序错乱
            if (states.size() == 1) {
                states.insert(states.begin(), proofs.at(0));
            }
        }

        nlohmann::json body = {
            { "id", track_result.id }, // TODO 行为id生成逻辑
            { "sn", track_result.camera_key },
            { "inoutType", track_result.inout },
            { "inoutTime", track_result.time_str }, 
            { "plateNumber", track_result.plate }, 
            { "plateColor", PLATE_COLOR_MAP[track_result.plate_color] }, 
            { "plateCredible", track_result.plate_conf / 100.f }, 
            { "actionCredible", track_result.inout_conf / 100.f }, 
            { "proofImgArray", proofs }, 
            { "stateImgArray", states },
            { "deviceBerthCode", track_result.berth },
            { "algType", 2 }, // 算法枚举值，智慧件用来区分上报行为的算法来源， 1 后端算法 2 前端相机算法 3 护栏算法 4 盒子算法
        };
        if (!track_result.plate_img.empty()) {
            std::string plate_base64 = Mat2Base64(track_result.plate_img, ".jpg");
            plate_base64.insert(0, "data:image/jpeg;base64,");
            body["plateUrl"] = plate_base64;
            track_result.plate_img.release();
        }
        std::string content = body.dump();

        auto res = httpClient()->Post(gc_app_config.base_config.middleware_upload_inout_api.c_str(), content, "application/json");
        int retry_time = 0;
        while ((res == nullptr || res->status != 200) && retry_time < 3) {
            usleep(100000);
            res = httpClient()->Post(gc_app_config.base_config.middleware_upload_inout_api.c_str(), content, "application/json");
            retry_time ++;
        }
        if (res == nullptr) {
            LOG_ERROR("POST upload inout api response is null, middleware_host=%s", gc_app_config.base_config.middleware_host.c_str());
        } else if (res->status != 200) {
            LOG_ERROR("POST upload inout api response status=%d, middleware_host=%s", res->status, gc_app_config.base_config.middleware_host.c_str());
        } else {
            try
            {
                result_json = nlohmann::json::parse(res->body.c_str());
            }
            catch(const std::exception& e)
            {
                LOG_ERROR("POST upload inout api parse response error, middleware_host=%s, response=%s", gc_app_config.base_config.middleware_host.c_str(), res->body.c_str());
            }
        }
        body.clear();
        content.clear();

        logger->info("Upload_inout_data_time: {:.3} s", sw);

        return result_json;
    }
}